/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.ext.zul;

import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.type.CrossRowVo;
import cn.easyplatform.type.FieldVo;
import cn.easyplatform.type.JmsObject;
import cn.easyplatform.utils.StringUtils;
import cn.easyplatform.web.ext.Runnable;
import cn.easyplatform.web.ext.*;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.*;
import org.zkoss.zul.event.PagingEvent;
import org.zkoss.zul.event.ZulEvents;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class ListboxExt extends Listbox implements ZkExt, Reloadable,
        Assignable {

    /**
     *
     */
    private static final long serialVersionUID = 8472104295591158010L;

    /**
     * 查询语句，可以有多个栏位
     */
    private String _query;

    /**
     * 数据连接的资源id
     */
    private String _dbId;

    /**
     * 分组查询
     */
    private String _groupQuery;

    /**
     * 在显示时是否要马上执行查询
     */
    private boolean _immediate = true;

    /**
     * 表示值的栏位
     */
    private String _valueField;

    /**
     * 分组显示标志
     */
    private String _groupField;

    /**
     * 分组名称标志
     */
    private String _groupNameField;

    /**
     * 列表头
     */
    private String _title;

    /**
     * 每一页的笔数
     */
    private int _pageSize = 20;

    /**
     * 排序
     */
    private String _orderBy;

    /**
     * 显示行数
     */
    private boolean _showRowNumbers;

    /**
     * 过滤表达式
     */
    private String _filter;

    /**
     * 分页组件是否显示详细信息
     */
    private boolean _pagingDetailed = true;

    /**
     * 分页组件显示模式
     */
    private String _pagingMold;

    /**
     * 分页组件显示style
     */
    private String _pagingStyle;

    /**
     * 头部风格
     */
    private String _headStyle;

    /**
     * 冻结的列数，从左到右开始
     */
    private int _frozenColumns;

    /**
     * 冻结头部风格
     */
    private String _frozenStyle = "background: #dfded8";

    /**
     * 行风格
     */
    private String _rowStyle;

    /**
     * 列头可否调整大小
     */
    private boolean _sizable = true;

    /**
     * 是否可拖拉
     */
    private String _draggable;

    /**
     * 有几个列表头
     */
    private int _cols;

    /**
     * 前台逐行脚本
     */
    private String _rowScript;

    /**
     * 需要统计的栏位，可以多个，以逗分号分隔
     */
    private String _totalColumns;

    /**
     * 最后总计表达式
     */
    private String _totalScript;

    /**
     * groupQuery不为空时是否延时加载分组数据
     */
    private boolean _lazy;

    /**
     * 当pageSize大于0，fetchAll为true，表示获取所有的数据，不需要分页，使用本身的分页
     */
    private boolean _fetchAll;

    /**
     * 是否以交叉表方式显示
     */
    private boolean _cross;

    /**
     * 可编辑栏位
     */
    private String _editableColumns;
    /**
     * 行表头查询
     */
    private String _rowQuery;
    /**
     * 是否必需重新加载
     */
    private boolean _force;
    /**
     * 联合多笔记录成一行
     */
    private int _unionRows = 1;

    public int getUnionRows() {
        return _unionRows;
    }

    public void setUnionRows(int unionRows) {
        this._unionRows = unionRows;
    }

    @Override
    public boolean isForce() {
        return _force;
    }

    public void setForce(boolean force) {
        this._force = force;
    }

    /**
     *
     */
    public ListboxExt() {
        super();
        setSpan(true);
    }

    public boolean isCross() {
        return _cross;
    }

    public void setCross(boolean cross) {
        this._cross = cross;
    }

    public String getEditableColumns() {
        return _editableColumns;
    }

    public void setEditableColumns(String editableColumns) {
        this._editableColumns = editableColumns;
    }

    public String getRowQuery() {
        return _rowQuery;
    }

    public void setRowQuery(String rowQuery) {
        this._rowQuery = rowQuery;
    }

    /**
     * @return the fetchAll
     */
    public boolean isFetchAll() {
        return _fetchAll;
    }

    /**
     * @param fetchAll the fetchAll to set
     */
    public void setFetchAll(boolean fetchAll) {
        this._fetchAll = fetchAll;
    }

    /**
     * @return the lazy
     */
    public boolean isLazy() {
        return _lazy;
    }

    /**
     * @param lazy the lazy to set
     */
    public void setLazy(boolean lazy) {
        this._lazy = lazy;
    }

    /**
     * @return the totalColumns
     */
    public String getTotalColumns() {
        return _totalColumns;
    }

    /**
     * @param totalColumns the totalColumns to set
     */
    public void setTotalColumns(String totalColumns) {
        this._totalColumns = totalColumns;
    }

    /**
     * @return the totalScript
     */
    public String getTotalScript() {
        return _totalScript;
    }

    /**
     * @param totalScript the totalScript to set
     */
    public void setTotalScript(String totalScript) {
        this._totalScript = totalScript;
    }

    /**
     * @return the rowScript
     */
    public String getRowScript() {
        return _rowScript;
    }

    /**
     * @param rowScript the rowScript to set
     */
    public void setRowScript(String rowScript) {
        this._rowScript = rowScript;
    }

    /**
     * @return the groupQuery
     */
    public String getGroupQuery() {
        return _groupQuery;
    }

    /**
     * @param groupQuery the groupQuery to set
     */
    public void setGroupQuery(String groupQuery) {
        this._groupQuery = groupQuery;
    }

    /**
     * @return the cols
     */
    public int getCols() {
        return _cols;
    }

    /**
     * @param cols the cols to set
     */
    public void setCols(int cols) {
        this._cols = cols;
    }

    /**
     * @return the draggable
     */
    public String getDraggable() {
        return _draggable;
    }

    /**
     * @param draggable the draggable to set
     */
    public void setDraggable(String draggable) {
        this._draggable = draggable;
    }

    /**
     * @return
     */
    public boolean isSizable() {
        return _sizable;
    }

    /**
     * @param sizable
     */
    public void setSizable(boolean sizable) {
        this._sizable = sizable;
    }

    /**
     * @return the pagingMold
     */
    public String getPagingMold() {
        return _pagingMold;
    }

    /**
     * @param pagingMold the pagingMold to set
     */
    public void setPagingMold(String pagingMold) {
        this._pagingMold = pagingMold;
    }

    /**
     * @return the pagingStyle
     */
    public String getPagingStyle() {
        return _pagingStyle;
    }

    /**
     * @param pagingStyle the pagingStyle to set
     */
    public void setPagingStyle(String pagingStyle) {
        this._pagingStyle = pagingStyle;
    }

    /**
     * @return the pagingDetailed
     */
    public boolean isPagingDetailed() {
        return _pagingDetailed;
    }

    /**
     * @param pagingDetailed the pagingDetailed to set
     */
    public void setPagingDetailed(boolean pagingDetailed) {
        this._pagingDetailed = pagingDetailed;
    }

    public String getFilter() {
        return _filter;
    }

    public void setFilter(String filter) {
        this._filter = filter;
    }

    public int getPageSize() {
        return _pageSize;
    }

    public void setPageSize(int pageSize) {
        this._pageSize = pageSize;
        reload();
    }

    /**
     * @return the orderBy
     */
    public String getOrderBy() {
        return _orderBy;
    }

    /**
     * @param orderBy the orderBy to set
     */
    public void setOrderBy(String orderBy) {
        this._orderBy = orderBy;
    }

    /**
     * @return
     */
    public boolean isShowRowNumbers() {
        return _showRowNumbers;
    }

    /**
     * @param showRowNumbers
     */
    public void setShowRowNumbers(boolean showRowNumbers) {
        this._showRowNumbers = showRowNumbers;
    }

    public String getValueField() {
        return _valueField;
    }

    public void setValueField(String valueField) {
        if (valueField != null)
            valueField = valueField.toUpperCase();
        this._valueField = valueField;
    }

    public String getTitle() {
        return _title;
    }

    public void setTitle(String title) {
        this._title = title;
    }

    public String getGroupField() {
        return _groupField;
    }

    public String getGroupNameField() {
        return _groupNameField;
    }

    public void setGroupField(String groupField) {
        if (groupField != null)
            groupField = groupField.toUpperCase();
        this._groupField = groupField;
    }

    public void setGroupNameField(String groupNameField) {
        if (groupNameField != null)
            groupNameField = groupNameField.toUpperCase();
        this._groupNameField = groupNameField;
    }

    public boolean isImmediate() {
        return _immediate;
    }

    public void setImmediate(boolean immediate) {
        this._immediate = immediate;
    }

    public String getQuery() {
        return _query;
    }

    public void setQuery(String query) {
        this._query = query;
    }

    public String getDbId() {
        return _dbId;
    }

    public void setDbId(String dbId) {
        this._dbId = dbId;
    }

    public String getRowStyle() {
        return _rowStyle;
    }

    public void setRowStyle(String rowStyle) {
        this._rowStyle = rowStyle;
    }

    public String getHeadStyle() {
        return _headStyle;
    }

    public void setHeadStyle(String headStyle) {
        this._headStyle = headStyle;
    }

    public int getFrozenColumns() {
        return _frozenColumns;
    }

    public void setFrozenColumns(int frozenColumns) {
        this._frozenColumns = frozenColumns;
    }

    public String getFrozenStyle() {
        return _frozenStyle;
    }

    public void setFrozenStyle(String frozenStyle) {
        this._frozenStyle = frozenStyle;
    }

    @Override
    public void setValue(Object value) {
        if (value != null) {
            if (value instanceof Object[]) {
                for (Object val : (Object[]) value) {
                    Iterator<Component> itr = queryAll("listitem").iterator();
                    boolean search = false;
                    while (itr.hasNext()) {
                        Listitem li = (Listitem) itr.next();
                        FieldVo[] fvs = li.getValue();
                        for (FieldVo field : fvs) {
                            if (field.getName().equals(getValueField())
                                    && Lang.equals(field.getValue(), val)) {
                                li.setSelected(true);
                                search = true;
                                break;
                            }
                        }
                        if (search)
                            break;
                    }
                }
            } else {
                Iterator<Component> itr = queryAll("listitem").iterator();
                while (itr.hasNext()) {
                    Listitem li = (Listitem) itr.next();
                    FieldVo[] fvs = li.getValue();
                    for (FieldVo field : fvs) {
                        if (field.getName().equals(getValueField())
                                && org.zkoss.lang.Objects.equals(field.getValue(), value)) {
                            li.setSelected(true);
                            return;
                        }
                    }
                }
            }
        }
    }


    @Override
    public Object getValue() {
        if (getSelectedCount() > 0) {
            if (isMultiple()) {
                Object[] val = new Object[getSelectedCount()];
                int index = 0;
                for (Listitem item : getSelectedItems()) {
                    FieldVo[] fvs = item.getValue();
                    if (fvs != null) {
                        for (FieldVo field : fvs) {
                            if (field.getName().equals(getValueField())) {
                                val[index++] = field.getValue();
                                break;
                            }
                        }
                    }
                }
                return val;
            } else {
                FieldVo[] fvs = getSelectedItem().getValue();
                if (fvs != null) {
                    for (FieldVo field : fvs) {
                        if (field.getName().equals(getValueField()))
                            return field.getValue();
                    }
                }
            }
        }
        return null;
    }

    public Object getData(String name, boolean sel) {
        if (Strings.isBlank(name))
            return null;
        name = name.toUpperCase();
        if (sel) {
            if (getSelectedIndex() == -1)
                return null;
            Listitem li = getSelectedItem();
            if (li != null && li.getValue() != null) {
                FieldVo[] fvs = li.getValue();
                for (FieldVo field : fvs) {
                    if (field.getName().equals(name))
                        return field.getValue();
                }
            }
            return null;
        } else {
            List<Object> data = new ArrayList<Object>();
            for (Listitem li : getItems()) {
                if (li.getValue() != null) {
                    FieldVo[] fvs = li.getValue();
                    for (FieldVo fv : fvs) {
                        if (fv.getName().equals(name)) {
                            data.add(fv.getValue());
                            break;
                        }
                    }
                }
            }
            Object[] objs = new Object[data.size()];
            data.toArray(objs);
            return objs;
        }
    }

    public Object getSelectedData() {
        if (getSelectedCount() > 0) {
            if (isMultiple()) {
                List<Object> data = new ArrayList<Object>();
                for (Listitem li : getSelectedItems()) {
                    if (li.getValue() != null) {
                        FieldVo[] fvs = li.getValue();
                        Object[] values = new Object[fvs.length];
                        for (int i = 0; i < values.length; i++)
                            values[i] = fvs[i].getValue();
                        data.add(values);
                    }
                }
                Object[] objs = new Object[data.size()];
                data.toArray(objs);
                return objs;
            } else {
                Listitem li = getSelectedItem();
                if (li.getValue() != null) {
                    FieldVo[] fvs = li.getValue();
                    Object[] values = new Object[fvs.length];
                    for (int i = 0; i < values.length; i++)
                        values[i] = fvs[i].getValue();
                    return values;
                }
            }
        }
        return null;
    }

    public Object[] getData() {
        List<Object> data = new ArrayList<Object>();
        for (Listitem li : getItems()) {
            if (li.getValue() != null) {
                FieldVo[] fvs = li.getValue();
                Object[] values = new Object[fvs.length];
                for (int i = 0; i < values.length; i++)
                    values[i] = fvs[i].getValue();
                data.add(values);
            }
        }
        Object[] objs = new Object[data.size()];
        data.toArray(objs);
        return objs;
    }

    public List<Object[]> getData(boolean sel) {
        List<Object[]> data = new ArrayList<Object[]>();
        if (sel) {
            if (getSelectedCount() > 0) {
                for (Listitem li : getSelectedItems()) {
                    FieldVo[] fvs = li.getValue();
                    if (fvs != null && li.isVisible())
                        data.add(fvs);
                }
            }
        } else {
            for (Listitem li : getItems()) {
                FieldVo[] fvs = li.getValue();
                if (fvs != null && li.isVisible())
                    data.add(fvs);
            }
        }
        return data;
    }


    public boolean setRowVisible(Object[] keyValue, boolean visible) {
        for (Listitem item : getItems()) {
            FieldVo[] fvs = item.getValue();
            if (fvs != null && Lang.equals(keyValue, fvs)) {
                item.setVisible(visible);
                return true;
            }
        }
        return false;
    }

    public void remove(Object[] data) {
        for (Listitem item : getItems()) {
            FieldVo[] fvs = item.getValue();
            if (fvs != null && Lang.equals(data, fvs)) {
                item.detach();
                break;
            }
        }
    }

    public void update(FieldVo[] fvs, int index) {
        Listitem item = getItemAtIndex(index);
        item.setValue(fvs);
        List<Listcell> cells = item.getChildren();
        for (int i = 0; i < cells.size(); i++) {
            Listcell lc = cells.get(i);
            lc.setLabel(fvs[i].getValue() == null ? "" : fvs[i].getValue()
                    .toString());
        }
    }

    @Override
    public void reload() {
        Widget ext = (Widget) getAttribute("$proxy");
        if (ext != null)
            ext.reload(this);
    }

    public void first() {
        Listitem li = getSelectedItem();
        if (li != null) {
            li.detach();
            getItems().add(0, li);
        }
    }

    public void next() {
        if (getSelectedCount() > 0)
            move(true);
    }

    public void last() {
        Listitem li = getSelectedItem();
        if (li != null) {
            li.detach();
            getItems().add(li);
        }
    }

    public void previous() {
        if (getSelectedCount() > 0)
            move(false);
    }

    public void move(boolean down) {
        Listitem li = getSelectedItem();
        int pos = getSelectedIndex();
        if (down)
            pos++;
        else
            pos--;
        if (pos < 0)
            pos = 0;
        else if (pos >= getItemCount())
            pos = getItemCount() - 1;
        li.detach();
        getItems().add(pos, li);
    }

    public Component setColumnName(int index, String name) {
        Listheader header = getHeader(index);
        header.setLabel(name);
        return header;
    }

    public Component setColumnVisible(int index, boolean visible) {
        Listheader header = getHeader(index);
        header.setVisible(visible);
        return header;
    }

    public Listheader getHeader(int index) {
        return (Listheader) getListhead().getChildren().get(index);
    }

    public void export(String type) {
        Exportable export = (Exportable) getAttribute("$proxy");
        export.export(type);
    }

    public synchronized Listitem appendItem(Object data) {
        if (data instanceof JmsObject && ((JmsObject) data).getObject() instanceof Map) {
            Map<String, Object> map = (Map<String, Object>) ((JmsObject) data).getObject();
            Listitem li = new Listitem();
            if (!Strings.isBlank(_title)) {
                String[] headers = _title.split(",");
                List<Listheader> listheaders = this.getListhead().getChildren();
                for (int i = 0; i < headers.length; i++) {
                    Object val = map.get(headers[i]);
                    Listcell lc = new Listcell();
                    lc.setParent(li);
                    if (val != null) {
                        String format = listheaders.get(i).getValue();
                        if (!Strings.isBlank(format)) {
                            lc.setLabel(StringUtils.format(format, val));
                        } else
                            lc.setLabel(val.toString());
                    }
                }
            } else {
                List<Listheader> headers = this.getListhead().getChildren();
                for (Listheader header : headers) {
                    String name = header.getValue();
                    if (!Strings.isBlank(name)) {
                        Object value = map.get(name);
                        Listcell lc = new Listcell(value == null ? "" : value + "");
                        lc.setParent(li);
                    }
                }
            }
            li.setValue(data);
            getItems().add(0, li);
            return li;
        }
        return null;
    }

    public synchronized Listitem removeItem(Object data) {
        int idx = -1;
        if (data instanceof String) {
            for (Listitem li : this.getItems()) {
                if (li.getValue() instanceof JmsObject) {
                    JmsObject obj = li.getValue();
                    if (obj.getMessageID().equals(data))
                        break;
                }
                idx++;
            }
        }
        if (idx < 0) {
            for (Listitem li : this.getItems()) {
                if (li.getValue() == data)
                    break;
                idx++;
            }
        }
        if (idx >= 0)
            return this.getItems().remove(idx);
        return null;
    }

    public synchronized Listitem removeItem() {
        Listitem li = getSelectedItem();
        if (li != null)
            li.detach();
        return li;
    }

    public FieldVo[] getRawValue() {
        Listitem li = getSelectedItem();
        if (li == null)
            return null;
        return li.getValue();
    }

    public List<CrossRowVo> getCrossData(boolean isAll) {
        List<CrossRowVo> data = new ArrayList<>();
        Iterator<Component> itr = queryAll("listcell").iterator();
        while (itr.hasNext()) {
            Listcell lc = (Listcell) itr.next();
            if (lc.getValue() != null && lc.getValue() instanceof CrossRowVo) {
                CrossRowVo crv = lc.getValue();
                if (isAll || crv.isModify())
                    data.add(crv);
            }
        }
        return data;
    }

    public void save(String logic) {
        if (!Strings.isBlank(logic) && isCross()) {
            Widget ext = (Widget) getAttribute("$proxy");
            if (ext != null) {
                try {
                    MethodUtils.invokeExactMethod(ext, "save", logic);
                } catch (Exception ex) {
                    throw Lang.wrapThrow(ex);
                }
            }
        }
    }

    /**
     * 执行指定的功能
     *
     * @param args
     */
    public void go(Object... args) {
        Runnable proxy = (Runnable) getAttribute("$proxy");
        proxy.go(args);
    }

    public Paging getPaging() {
        return _paging;
    }

    public void setPaging(Paging paging) {
        this._paging = paging;
    }

    public void repeat() {
        if (_paging != null) {
            int ap = _paging.getActivePage();
            if (ap == _paging.getPageCount() - 1)
                ap = 0;
            else
                ap = _paging.getActivePage() + 1;
            _paging.setActivePage(ap);
            if (this.getPaginal() == null)
                Events.postEvent(new PagingEvent(ZulEvents.ON_PAGING, _paging, ap));
        }
    }

    private Paging _paging;
}
